import fg from "fast-glob";
import fs from "fs-extra";
import path from "path";
import { UnityHelper } from "./UnityHelper.js";
import _ from "lodash";
import { Record } from "./Record.js";
import { toolchain } from "../toolchain.js";
import { SVN } from "./SVN.js";

export class UIStriper {
    public async removeUseless(): Promise<void> {
        const assetSourcesRoot = path.join(toolchain.opts.projectRoot, 'Assets/AssetSources');
        const keepMap: { [file: string]: true } = {};
        const cfgHitMap: { [p: string]: true } = {};
        const usedBySrcMap: { [prefab: string]: true } = {};
        const configuredPaths: string[] = [];

        if (toolchain.opts.cfg != null) {
            // 部分写死保留的prefab
            if (toolchain.opts.cfg.keep != null) {
                for (const k of toolchain.opts.cfg.keep) {
                    const keeps = await fg(k, { cwd: toolchain.opts.projectRoot });
                    for (const f of keeps) {
                        keepMap[path.normalize(f)] = true;
                    }
                }
            }

            // 读入表格使用的模型
            if (toolchain.opts.cfg.prefabCfg != null) {
                for (const pc of toolchain.opts.cfg.prefabCfg) {
                    if (pc.disabled) continue;
                    cfgHitMap[pc.path] = true;
                    if (pc.path != '*') {
                        configuredPaths.push(pc.path.substring(0, pc.path.indexOf('*')));
                    }
                    for (const rule of pc.rules) {
                        let jfiles: string[];
                        if (rule.cfgFile.includes('*')) {
                            // 包含多个json文件
                            jfiles = await fg(rule.cfgFile, { cwd: assetSourcesRoot });
                            jfiles = jfiles.map((v) => path.join(assetSourcesRoot, v));
                        } else {
                            jfiles = [path.join(assetSourcesRoot, 'data', rule.cfgFile)];
                        }
                        for (let jf of jfiles) {
                            let cfgs = await fs.readJson(jf);
                            if (!(cfgs instanceof Array)) {
                                cfgs = [cfgs];
                            }
                            const keys = rule.cfgKey.split('.');
                            const values = this.getMatches(cfgs, keys);
                            for (const v of values) {
                                const p = pc.path.replaceAll('*', v).toLowerCase();
                                // if (p.includes('model/chatBubble')) {
                                //     console.log('kkk');
                                // }
                                usedBySrcMap[p] = true;
                                usedBySrcMap[p.replace('.prefab', 'b.prefab')] = true;
                                usedBySrcMap[p.replace('.prefab', '_b.prefab')] = true;
                            }
                        }
                    }
                }
            }
        } else {
            console.error('no cfg!');
            process.exit(1);
        }
        
        // 收集ts代码/cs代码引用到的ui prefab
        //#region 
        const srcs = await fg(['TsScripts/**/*.ts', 'Assets/**/*.cs'], { cwd: toolchain.opts.projectRoot });
        const variables: string[] = [], notExists: string[] = [], potentialPrefabs: string[] = [], potentialPaths: string[] = [], exactlyNames: string[] = [];

        const allMchs: string[] = [];
        for (const src of srcs) {
            const file = path.join(toolchain.opts.projectRoot, src);
            const content = await fs.readFile(file, 'utf-8');
            const lines = content.split(/\r?\n/);
            for (const line of lines) {
                if (line.startsWith('import ') || line.includes('this.elems.get') || line.includes('ElemFinder.')) continue;
                const mchs = line.matchAll(/('|"|`)\S*?\1/g);
                for (const mch of mchs) {
                    const s = mch[0];
                    if (s.startsWith('.') || s.startsWith('\\') || s.startsWith('http://') || s.startsWith('https://') || s.startsWith('images/') || s.startsWith('com/') || s.search(/[\w]/) < 0 || s.search(/[\u4e00-\u9fa5]+/) >= 0) continue;
                    if (!allMchs.includes(s)) {
                        allMchs.push(s);
                    }
                }
            }
        }
        for (const mch of allMchs) {
            // 尝试进行通配检测
            const quoted = mch.substring(1, mch.length - 1);
            // if (quoted == 'Draw_3b') {
            //     console.log('aaa');
            // }
            const extn = path.extname(quoted);
            if (extn == '.prefab') {
                // 明确是prefab                   
                const str = quoted.replaceAll(/\{\d+\}/g, '*').replaceAll(/\$\{\w+\}/g, '*').replaceAll(/"\s*\+\s*\w+\s*\+\s*"/g, '*');
                if (str == '.prefab' || str == '*.prefab' || str.includes('*/*/')) continue;
                if (cfgHitMap[str]) continue;

                let files: string[];
                if (str.includes('*')) {
                    files = [];
                    // 检查是否有关联配置信息
                    if (!configuredPaths.includes(str.substring(0, str.indexOf('*')))) {
                        const hits = await fg(str, { cwd: assetSourcesRoot });
                        if (hits.length > 0) {
                            files = hits;
                        } else {
                            if (!variables.includes(str)) variables.push(str);
                            continue;
                        }
                    }
                } else {
                    files = [str];
                }

                for (const f of files) {
                    const fp = path.join(assetSourcesRoot, f);
                    if (fs.existsSync(fp)) {
                        // if (f.includes('model/chatBubble')) {
                        //     console.log('kkk');
                        // }
                        usedBySrcMap[f.toLowerCase()] = true;
                    } else {
                        if (!notExists.includes(f)) notExists.push(f);
                    }
                }
            } else if (extn == '') {
                // 有可能是prefab
                if (quoted.startsWith('Assets/')) {
                    if (!potentialPaths.includes(quoted)) {
                        potentialPaths.push(quoted);
                    }
                } else if (quoted.length > 1 && quoted.search(/[\s=\?:]/) < 0) {
                    if (quoted.includes('/')) {
                        let pattern = `**/${quoted}`;
                        if (!quoted.endsWith('.prefab')) {
                            pattern += '.prefab';
                        }
                        const mchs = await fg(pattern, { cwd: assetSourcesRoot });
                        for (const mch of mchs) {
                            if (!potentialPrefabs.includes(mch)) {
                                potentialPrefabs.push(mch);
                            }
                        }
                    } else {
                        exactlyNames.push(quoted);
                    }
                }
            }
        }

        console.log('-----------------------------');
        notExists.forEach((v) => console.log('prefab not exists:', v));
        console.log('-----------------------------');
        variables.forEach((v) => console.log('[Attention!] Variable prefab hit nothing:', v));
        console.log('-----------------------------');
        //#endregion

        // 收集用到的guid
        //#region 
        const usedGUIDMap = await UnityHelper.collectUsedGUID(toolchain.opts.projectRoot);
        //#endregion
        
        // 删除没用的ui prefab，既不被代码引用，又不被其他prefab引用判定为没用
        //#region 
        const svn = new SVN();

        const prefabRoots = ['Assets/AssetSources', 'Assets/Arts'];
        const toBeDeleteds: { guid: string, file: string, date?: string }[] = [];
        for (const pr of prefabRoots) {
            const prefabs = await fg('**/*.prefab', { cwd: path.join(toolchain.opts.projectRoot, pr) });
            for (const prefab of prefabs) {
                const f = path.join(pr, prefab);
                if (keepMap[f]) continue;
                // if (prefab.endsWith('expression_but.prefab')) {
                //     console.log('ggg');
                // }
                if (!usedBySrcMap[prefab.toLowerCase()]) {
                    // 没在代码里引用，检查是否被其他prefab引用
                    const file = path.join(toolchain.opts.projectRoot, f);
                    const guid = await UnityHelper.readGUID(file + '.meta');
                    if (!usedGUIDMap[guid]) {
                        // 也没被其他prefab引用，检查是否代码里通过拼接方式使用
                        const pn = path.dirname(prefab), bn = path.basename(prefab, '.prefab');
                        if (!potentialPaths.includes(pn) && !potentialPrefabs.includes(prefab) && !exactlyNames.includes(bn)) {
                            // 确认没用，查看提交时间，如果是7天内提交的，则保留，否则直接删除
                            const reserved = await svn.isModifiedRecent(file, 7);
                            if (reserved.result) {
                                console.log('Though not used but modified recent, reserved: ' + prefab);
                                continue;
                            }
                            toBeDeleteds.push({ guid, file, date: reserved.date });
                        }
                    }
                }
            }
        }
        //#endregion

        if (toolchain.opts.output) {
            // 不删除，只输出哪些文件，方便执行deleteFile命令
            let content = '';
            for (const d of toBeDeleteds) {
                content += d.file + '  ' + (d.date ?? '[no date]') + '\n';
            }
            await fs.writeFile(toolchain.opts.output, content, 'utf-8');
            console.log(`${toBeDeleteds.length} prefab found, see: ${toolchain.opts.output}`);
        } else {
            for (const d of toBeDeleteds) {
                await fs.unlink(d.file);
                await fs.unlink(d.file + '.meta');
                await Record.Instance.recordGUID(d.guid, d.file);
                // console.log('remove prefab:', file);
            }
            console.log('deleted ui prefabs:', toBeDeleteds.length);
        }
    }

    private getMatches<T = any>(arr: T[], keys: string[]): any[] {
        const out: any[] = [];
        for (const item of arr) {
            const sitem = item[keys[0] as keyof T];
            let sitemArr: T[keyof T][];
            if (sitem instanceof Array) {
                sitemArr = sitem;
            } else {
                sitemArr = [sitem];
            }
            if (keys.length > 1) {
                out.push(...this.getMatches(sitemArr, keys.slice(1)));
            } else {
                out.push(...sitemArr)
            }
        }
        return out;
    }
}
